#include "stream.h"
#include <streambuf>
#include <vector>
#include <type_traits>
#include <zlib.h>
#include "public/utils.h"

/* Files */

class FileInput: public StreamBuf {
	FILE* file;
	char buffer[BUFSIZ];
	bool eof;

	std::streamsize read_from_file(char* s, std::streamsize n) {
		std::streamsize ret=0;
		if(eof)
			return ret;
		if(n>0) {
			std::streamsize r=fread(s, sizeof(char), n, file);
			ret+=r;
			if(r<n) {
				if(ferror(file))
					throwError("failed to read file");
				if(feof(file))
					eof=true;
			}
		}
		return ret;
	}

	protected:
	std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode which) override {
		std::streamoff ret=-1;
		if(!file)
			return ret;
		if(!(std::ios_base::in&which))
			return ret;
		int whence=SEEK_SET;
		if(way==std::ios_base::cur) {
			if(off+gptr()<=egptr() && off+gptr()>=eback()) {
#ifdef WIN32
				ret = _ftelli64(file);
#else
				ret = ftello(file);
#endif
				if(ret!=-1) {
					gbump(off);
					ret-=egptr()-gptr();
				}
				return ret;
			}
			whence=SEEK_CUR;
			off-=egptr()-gptr();
		} else if(way==std::ios_base::end) {
			whence=SEEK_END;
		}
#ifdef WIN32
		auto r = _fseeki64(file, off, whence);
#else
		auto r = fseeko(file, off, whence);
#endif
		if(r!=-1) {
			if(eof)
				eof=false;
			setg(buffer, buffer, buffer);
#ifdef WIN32
			ret = _ftelli64(file);
#else
			ret = ftello(file);
#endif
		}
		return ret;
	}
	std::streampos seekpos(std::streampos sp, std::ios_base::openmode which) override {
		std::streamoff ret=-1;
		if(!file)
			return ret;
		if(!(std::ios_base::in&which))
			return ret;
#ifdef WIN32
		auto r = _fseeki64(file, sp, SEEK_SET);
#else
		auto r = fseeko(file, sp, SEEK_SET);
#endif
		if(r!=-1) {
			if(eof)
				eof=false;
			setg(buffer, buffer, buffer);
#ifdef WIN32
			ret = _ftelli64(file);
#else
			ret = ftello(file);
#endif
		}
		return ret;
	}
	int sync() override { return 0; }
	std::streamsize showmanyc() override {
		std::streamsize ret=egptr()-gptr();
		if(!file || (eof && ret==0)) {
			ret=-1;
		}
		return ret;
	}
	std::streamsize xsgetn(char* s, std::streamsize n) override {
		if(!file)
			return 0;
		std::streamsize total=n;
		auto avail=egptr()-gptr();
		if(n>=BUFSIZ+avail) { // big
			if(avail) {
				traits_type::copy(s, gptr(), avail);
				setg(eback(), egptr(), egptr());
				s+=avail;
				n-=avail;
			}
			// just read blocks directly
			auto toread=n/BUFSIZ*BUFSIZ;
			auto r=read_from_file(s, toread);
			s+=r;
			n-=r;
			if(r==toread && n>0) {
				n-=StreamBuf::xsgetn(s, n);
			}
		} else if(n>0) {
			n-=StreamBuf::xsgetn(s, n);
		}
		return total-n;
	}
	int underflow() override {
		int ret=EOF;
		if(!file)
			return ret;
		if(gptr()<egptr()) {
			ret=traits_type::to_int_type(*gptr());
		} else {
			auto r=read_from_file(buffer, BUFSIZ);
			if(r!=0) {
				setg(buffer, buffer, buffer+r);
				ret=traits_type::to_int_type(*gptr());
			}
		}
		return ret;
	}
	int pbackfail(int c) override {
		int ret=EOF;
		if(!file)
			return ret;
		if(eback()<gptr()) {
			gbump(-1);
			if(c==EOF)
				ret=0;
			else
				ret=c;
		} else {
#ifdef WIN32
			if (_fseeki64(file, gptr() - egptr() - 1, SEEK_CUR) != -1) {
#else
			if (fseeko(file, gptr() - egptr() - 1, SEEK_CUR) != -1) {
#endif
				if(eof)
					eof=false;
				setg(buffer, buffer, buffer);
				auto r=underflow();
				if(r!=EOF) {
					if(c==EOF)
						ret=0;
					else
						ret=c;
				}
			}
		}
		return ret;
	}
	std::streamsize xsputn(const char* s, std::streamsize n) override { return 0; }
	int overflow(int c) override { return EOF; }

	public:
	FileInput(const char* fn):
		StreamBuf{}, file{nullptr}, buffer{}, eof{false}
	{
		file=fopen(fn, "rb");
		if(!file) {
			throwError("Failed to open file.");
		}
		setvbuf(file, nullptr, _IONBF, 0);
		setg(buffer, buffer, buffer);
	}
	FileInput(FILE* f):
		StreamBuf{}, file{f}, buffer{}, eof{false}
	{
		if(!file) {
			throwError("Failed to open file.");
		}
		setvbuf(file, nullptr, _IONBF, 0);
		setg(buffer, buffer, buffer);
	}
	~FileInput() {
		if(file)
			fclose(file);
	}
	bool close() override {
		if(!file)
			return true;
		if(fclose(file)!=0)
			return false;
		file=nullptr;
		return true;
	}
};

class FileOutput: public StreamBuf {
	FILE* file;
	char buffer[BUFSIZ];

	bool flush_buffer() {
		bool ret=true;
		if(pbase()<pptr()) {
			ret=false;
			auto towrite=pptr()-pbase();
			auto r=write_to_file(pbase(), towrite);
			if(r>0) {
				if(r<towrite) {
					setp(pbase()+r, epptr());
					pbump(towrite-r);
				} else {
					ret=true;
					setp(buffer, buffer+BUFSIZ);
				}
			}
		}
		return ret;
	}
	std::streamsize write_to_file(const char* s, std::streamsize n) {
		std::streamsize ret=0;
		if(n>0) {
			std::streamsize r=fwrite(s, sizeof(char), n, file);
			ret+=r;
			if(r<n) {
				if(ferror(file))
					throwError("failed to write file");
			}
		}
		return ret;
	}

	protected:
	std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode which) override {
		std::streamoff ret=-1;
		if(!file)
			return ret;
		if(!(std::ios_base::out&which))
			return ret;
		int whence=SEEK_SET;
		if(way==std::ios_base::cur) {
			if(off==0) {
#ifdef WIN32
				ret = _ftelli64(file);
#else
				ret = ftello(file);
#endif
				if(ret!=-1)
					ret+=pptr()-pbase();
				return ret;
			}
			whence=SEEK_CUR;
		} else if(way==std::ios_base::end) {
			whence=SEEK_END;
		}
		if(flush_buffer()) {
#ifdef WIN32
			auto r = _fseeki64(file, off, whence);
			if (r != -1) {
				ret = _ftelli64(file);
			}
#else
			auto r = fseeko(file, off, whence);
			if (r != -1) {
				ret = ftello(file);
			}
#endif
		}
		return ret;
	}
	std::streampos seekpos(std::streampos sp, std::ios_base::openmode which) override {
		std::streamoff ret=-1;
		if(!file)
			return ret;
		if(!(std::ios_base::out&which))
			return ret;
		if(flush_buffer()) {
#ifdef WIN32
			auto r = _fseeki64(file, sp, SEEK_SET);
			if (r != -1) {
				ret = _ftelli64(file);
			}
#else
			auto r = fseeko(file, sp, SEEK_SET);
			if (r != -1) {
				ret = ftello(file);
			}
#endif
		}
		return ret;
	}
	int sync() override {
		int ret=-1;
		if(!file)
			return ret;
		if(flush_buffer()) {
			if(fflush(file)==0) {
				ret=0;
			}
		}
		return ret;
	}
	std::streamsize showmanyc() override { return -1; }
	std::streamsize xsgetn(char* s, std::streamsize n) override { return 0; }
	int underflow() override { return EOF; }
	int pbackfail(int c) override { return EOF; }
	std::streamsize xsputn(const char* s, std::streamsize n) override {
		if(!file)
			return 0;
		std::streamsize total=n;
		auto slot=epptr()-pptr();
		if(n<BUFSIZ+slot) {
			n-=StreamBuf::xsputn(s, n);
		} else {
			traits_type::copy(pptr(), s, slot);
			pbump(slot);
			s+=slot;
			n-=slot;
			if(flush_buffer()) {
				auto towrite=n/BUFSIZ*BUFSIZ;
				auto r=write_to_file(s, towrite);
				s+=r;
				n-=r;
				if(auto rem=r%BUFSIZ)
					setp(buffer+rem, buffer+BUFSIZ);
				auto tocopy=epptr()-pptr();
				if(tocopy>n)
					tocopy=n;
				traits_type::copy(pptr(), s, tocopy);
				s+=tocopy;
				n-=tocopy;
				pbump(tocopy);
			}
		}
		return total-n;
	}
	int overflow(int c) override {
		int ret=EOF;
		if(!file)
			return ret;
		if(flush_buffer()) {
			if(c==EOF) {
				ret=0;
			} else {
				*pptr()=traits_type::to_char_type(c);
				pbump(1);
				ret=c;
			}
		}
		return ret;
	}

	public:
	FileOutput(const char* fn):
		StreamBuf{}, file{nullptr}, buffer{}
	{
		file=fopen(fn, "wb");
		if(!file) {
			throwError("Failed to open file.");
		}
		setvbuf(file, nullptr, _IONBF, 0);
		setp(buffer, buffer+BUFSIZ);
	}
	FileOutput(FILE* f):
		StreamBuf{}, file{f}, buffer{}
	{
		if(!file) {
			throwError("Failed to open file.");
		}
		setvbuf(file, nullptr, _IONBF, 0);
		setp(buffer, buffer+BUFSIZ);
	}
	~FileOutput() {
		if(file) {
			flush_buffer();
			fclose(file);
		}
	}
	bool close() override {
		if(!file)
			return true;
		if(!flush_buffer())
			return false;
		if(fclose(file)!=0)
			return false;
		file=nullptr;
		return true;
	}
};

/* Filters */

template<typename T>
class FilterInput: public StreamBuf {
	StreamBuf*const parent;
	T filter;
	char buffer[BUFSIZ];
	bool is_open;
	bool eof;
	FilterInput(StreamBuf*const p): StreamBuf{}, parent{p}, filter{}, buffer{}, is_open{false}, eof{false} {
		setg(buffer, buffer, buffer);
	}
	
	std::streamsize read_from_upstream(char* s, std::streamsize n);
	std::streamoff position();

	protected:
	std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode which) override {
		std::streamoff ret=-1;
		if(!is_open)
			return ret;
		if(!(std::ios_base::in&which))
			return ret;
		if(way==std::ios_base::cur) {
			if(off+gptr()<=egptr() && off+gptr()>=eback()) {
				gbump(off);
				ret=position()-(egptr()-gptr());
			}
		}
		return ret;
	}
	std::streampos seekpos(std::streampos sp, std::ios_base::openmode which) override {
		return -1;
	}
	int sync() override { return 0; }
	std::streamsize showmanyc() override {
		if(!is_open)
			return -1;
		std::streamsize ret=egptr()-gptr();
		if(eof) {
			if(ret==0) {
				ret=-1;
			}
		}
		return ret;
	}
	std::streamsize xsgetn(char* s, std::streamsize n) override {
		if(!is_open)
			return 0;
		std::streamsize total=n;
		auto avail=egptr()-gptr();
		if(n>=BUFSIZ+avail) { // big
			if(avail) {
				traits_type::copy(s, gptr(), avail);
				setg(eback(), egptr(), egptr());
				s+=avail;
				n-=avail;
			}
			// just read blocks directly
			auto toread=n/BUFSIZ*BUFSIZ;
			auto r=read_from_upstream(s, toread);
			s+=r;
			n-=r;
			if(r==toread && n>0) {
				n-=StreamBuf::xsgetn(s, n);
			}
		} else if(n>0) {
			n-=StreamBuf::xsgetn(s, n);
		}
		return total-n;
	}
	int underflow() override {
		int ret=EOF;
		if(!is_open)
			return ret;
		if(gptr()<egptr()) {
			ret=traits_type::to_int_type(*gptr());
		} else {
			auto r=read_from_upstream(buffer, BUFSIZ);
			if(r!=0) {
				setg(buffer, buffer, buffer+r);
				ret=traits_type::to_int_type(*gptr());
			}
		}
		return ret;
	}
	int pbackfail(int c) override {
		int ret=EOF;
		if(!is_open)
			return ret;
		if(eback()<gptr()) {
			gbump(-1);
			if(c==EOF)
				ret=0;
			else
				ret=c;
		}
		return ret;
	}
	std::streamsize xsputn(const char* s, std::streamsize n) override { return 0; }
	int overflow(int c) override { return EOF; }

	public:
	FilterInput(StreamBuf*const p, const typename T::Params& pars);
	~FilterInput() override;
	bool close() override;
};

template<typename T>
class FilterOutput: public StreamBuf {
	StreamBuf*const parent;
	T filter;
	char buffer[BUFSIZ];
	bool is_open;
	FilterOutput(StreamBuf*const p): StreamBuf{}, parent{p}, filter{}, buffer{}, is_open{false} {
		setp(buffer, buffer+BUFSIZ);
	}

	bool flush_buffer() {
		bool ret=true;
		if(pbase()<pptr()) {
			ret=false;
			auto towrite=pptr()-pbase();
			auto r=write_to_downstream(pbase(), towrite);
			if(r>0) {
				if(r<towrite) {
					setp(pbase()+r, epptr());
					pbump(towrite-r);
				} else {
					ret=true;
					setp(buffer, buffer+BUFSIZ);
				}
			}
		}
		return ret;
	}
	bool flush_filter();
	bool finish_filter();
	std::streamsize write_to_downstream(const char* s, std::streamsize n);
	std::streamoff position();

	protected:
	std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode which) override {
		std::streamoff ret=-1;
		if(!is_open)
			return ret;
		if(!(std::ios_base::out&which))
			return ret;
		if(way==std::ios_base::cur) {
			if(off<=0 && pptr()+off>=pbase()) {
				pbump(off);
				ret=position()+pptr()-pbase();
			}
		}
		return ret;
	}
	std::streampos seekpos(std::streampos sp, std::ios_base::openmode which) override {
		return -1;
	}
	int sync() override {
		int ret=-1;
		if(!is_open)
			return ret;
		if(flush_filter()) {
			ret=parent->sync();
		}
		return ret;
	}
	std::streamsize showmanyc() override { return -1; }
	std::streamsize xsgetn(char* s, std::streamsize n) override { return 0; }
	int underflow() override { return EOF; }
	int pbackfail(int c) override { return EOF; }
	std::streamsize xsputn(const char* s, std::streamsize n) override {
		if(!is_open)
			return 0;
		std::streamsize total=n;
		auto slot=epptr()-pptr();
		if(n<BUFSIZ+slot) {
			n-=StreamBuf::xsputn(s, n);
		} else {
			traits_type::copy(pptr(), s, slot);
			pbump(slot);
			s+=slot;
			n-=slot;
			if(flush_buffer()) {
				auto towrite=n/BUFSIZ*BUFSIZ;
				auto r=write_to_downstream(s, towrite);
				s+=r;
				n-=r;
				if(auto rem=r%BUFSIZ)
					setp(buffer+rem, buffer+BUFSIZ);
				auto tocopy=epptr()-pptr();
				if(tocopy>n)
					tocopy=n;
				traits_type::copy(pptr(), s, tocopy);
				s+=tocopy;
				n-=tocopy;
				pbump(tocopy);
			}
		}
		return total-n;
	}
	int overflow(int c) override {
		int ret=EOF;
		if(!is_open)
			return ret;
		if(flush_buffer()) {
			if(c==EOF) {
				ret=0;
			} else {
				*pptr()=traits_type::to_char_type(c);
				pbump(1);
				ret=c;
			}
		}
		return ret;
	}

	public:
	FilterOutput(StreamBuf*const p, const typename T::Params& par);
	~FilterOutput();
	bool close() override;
};

/* Gzip */

struct GzipDecompress {
	z_stream strm;
	struct Params {
		int windowBits;
	};
};
template<>
inline bool FilterInput<GzipDecompress>::close() {
	if(!is_open)
		return true;
	if(!eof) {
		bool error=false;
		size_t bytes_skipped=0;
		do {
			filter.strm.next_out=reinterpret_cast<unsigned char*>(buffer);
			filter.strm.avail_out=BUFSIZ;
			int flush=Z_NO_FLUSH;
			auto in_delta=filter.strm.avail_in;
			if(in_delta==0) {
				auto c=parent->underflow();
				if(c==EOF) {
					flush=Z_FINISH;
				} else {
					filter.strm.next_in=reinterpret_cast<unsigned char*>(parent->gptr());
					in_delta=filter.strm.avail_in=parent->egptr()-parent->gptr();
				}
			}
			auto r=inflate(&filter.strm, flush);
			in_delta-=filter.strm.avail_in;
			parent->gbump(in_delta);
			switch(r) {
				case Z_OK:
					// continue
					break;
				case Z_BUF_ERROR:
					error=true;
					break;
				case Z_STREAM_END:
					eof=true;
					break;
				default:
					throwError("failed to inflate");
			}
			bytes_skipped+=BUFSIZ-filter.strm.avail_out;
		} while(!eof && !error);
		if(error)
			throwError("Failed to skip gzip stream.");
		printMessage("Skipped ", bytes_skipped, " bytes.");
	}
	auto r=inflateEnd(&filter.strm);
	if(r!=Z_OK)
		throwError("Failed to destroy gzip input filter: ", filter.strm.msg);
	is_open=false;
	return true;
}
template<>
inline FilterInput<GzipDecompress>::~FilterInput() {
	if(is_open) {
		auto r=inflateEnd(&filter.strm);
		if(r!=Z_OK) {
			throwError("Failed to destroy gzip input filter: ", filter.strm.msg);
		}
	}
}
template<>
inline FilterInput<GzipDecompress>::FilterInput(StreamBuf*const p, const GzipDecompress::Params& args): FilterInput{p} {
	filter.strm.next_in=reinterpret_cast<unsigned char*>(parent->gptr());
	filter.strm.avail_in=parent->egptr()-parent->gptr();
	filter.strm.next_out=reinterpret_cast<unsigned char*>(buffer);
	filter.strm.avail_out=0;
	filter.strm.zalloc=nullptr;
	filter.strm.zfree=nullptr;
	filter.strm.opaque=0;
	auto r=inflateInit2(&filter.strm, args.windowBits);
	if(r!=Z_OK) {
		throwError("Failed to init gzip input filter: ", filter.strm.msg);
	}
	is_open=true;
}
template<>
inline std::streamsize FilterInput<GzipDecompress>::read_from_upstream(char* s, std::streamsize n) {
	if(eof)
		return 0;
	if(n<=0)
		return 0;
	filter.strm.next_out=reinterpret_cast<unsigned char*>(s);
	filter.strm.avail_out=n;
	bool stop=false;
	do {
		int flush=Z_NO_FLUSH;
		auto in_delta=filter.strm.avail_in;
		if(in_delta==0) {
			auto c=parent->underflow();
			if(c==EOF) {
				flush=Z_FINISH;
			} else {
				filter.strm.next_in=reinterpret_cast<unsigned char*>(parent->gptr());
				in_delta=filter.strm.avail_in=parent->egptr()-parent->gptr();
			}
		}
		auto r=inflate(&filter.strm, flush);
		in_delta-=filter.strm.avail_in;
		parent->gbump(in_delta);
		switch(r) {
			case Z_OK:
				// continue
				break;
			case Z_BUF_ERROR:
				stop=true;
				break;
			case Z_STREAM_END:
				eof=true;
				break;
			default:
				throwError("failed to inflate");
		}
	} while(filter.strm.avail_out>0 && !eof && !stop);
	auto out_delta=n-filter.strm.avail_out;
	return out_delta;
}
template<>
std::streamoff FilterInput<GzipDecompress>::position() {
	std::streamoff pos=filter.strm.total_out;
	return pos;
}

struct GzipCompress {
	z_stream strm;
	struct Params {
		int  level;
		int  method;
		int  windowBits;
		int  memLevel;
		int  strategy;
	};
};

template<>
inline bool FilterOutput<GzipCompress>::finish_filter() {
	bool ret=false;
	auto toflush=pptr()-pbase();
	filter.strm.next_in=reinterpret_cast<unsigned char*>(pbase());
	filter.strm.avail_in=toflush;
	bool flushed=false;
	do {
		int flush=Z_FINISH;
		auto out_delta=filter.strm.avail_out;
		if(out_delta==0) {
			auto c=parent->overflow(EOF);
			if(c==EOF) {
				// XXX
			} else {
				filter.strm.next_out=reinterpret_cast<unsigned char*>(parent->pptr());
				out_delta=filter.strm.avail_out=parent->epptr()-parent->pptr();
			}
		}
		auto r=deflate(&filter.strm, flush);
		out_delta-=filter.strm.avail_out;
		parent->pbump(out_delta);
		switch(r) {
			case Z_OK:
				// continue
				break;
			case Z_STREAM_END:
				flushed=true;
				break;
			case Z_BUF_ERROR:
				break;
			default:
				throwError("failed to deflate");
		}
	} while(!flushed);
	auto delta_in=toflush-filter.strm.avail_in;
	if(delta_in<toflush) {
		if(delta_in>0) {
			setp(pbase()+delta_in, epptr());
			pbump(toflush-delta_in);
		}
	} else {
		ret=true;
		setp(buffer, buffer+BUFSIZ);
	}
	return ret;
}
template<>
inline bool FilterOutput<GzipCompress>::close() {
	if(!is_open)
		return true;
	if(!finish_filter())
		return false;
	auto r=deflateEnd(&filter.strm);
	if(r!=Z_OK) {
		throwError("Failed to destroy gzip output filter: ", filter.strm.msg);
	}
	is_open=false;
	return true;
}
template<>
FilterOutput<GzipCompress>::~FilterOutput() {
	if(is_open) {
		finish_filter();
		auto r=deflateEnd(&filter.strm);
		if(r!=Z_OK) {
			throwError("Failed to destroy gzip output filter: ", filter.strm.msg);
		}
	}
}
template<>
FilterOutput<GzipCompress>::FilterOutput(StreamBuf*const p, const GzipCompress::Params& pars): FilterOutput{p} {
	filter.strm.next_in=reinterpret_cast<unsigned char*>(pbase());
	filter.strm.avail_in=0;
	filter.strm.next_out=reinterpret_cast<unsigned char*>(parent->pptr());
	filter.strm.avail_out=parent->epptr()-parent->pptr();
	filter.strm.zalloc=nullptr;
	filter.strm.zfree=nullptr;
	filter.strm.opaque=nullptr;
	auto r=deflateInit2(&filter.strm, pars.level, pars.method, pars.windowBits, pars.memLevel, pars.strategy);
	if(r!=Z_OK)
		throwError("Failed to init gzip output filter: ", filter.strm.msg);
	is_open=true;
}

template<>
std::streamoff FilterOutput<GzipCompress>::position() {
	std::streamoff pos=filter.strm.total_in;
	return pos;
}

template<>
inline std::streamsize FilterOutput<GzipCompress>::write_to_downstream(const char* s, std::streamsize n) {
	if(n<=0)
		return 0;
	filter.strm.next_in=reinterpret_cast<unsigned char*>(const_cast<char*>(s));
	filter.strm.avail_in=n;
	bool stop=false;
	do {
		int flush=Z_NO_FLUSH; // Z_SYNC_FLUSH, Z_FINISH
		auto out_delta=filter.strm.avail_out;
		if(out_delta==0) {
			auto c=parent->overflow(EOF);
			if(c==EOF) {
				// XXX
			} else {
				filter.strm.next_out=reinterpret_cast<unsigned char*>(parent->pptr());
				out_delta=filter.strm.avail_out=parent->epptr()-parent->pptr();
			}
		}
		auto r=deflate(&filter.strm, flush);
		out_delta-=filter.strm.avail_out;
		parent->pbump(out_delta);
		switch(r) {
			case Z_OK:
				// continue
				break;
			case Z_BUF_ERROR:
				stop=true;
				break;
			case Z_STREAM_END:
			default:
				throwError("failed to deflate");
		}
	} while(filter.strm.avail_in>0 && !stop);
	auto delta_in=n-filter.strm.avail_in;
	return delta_in;
}
template<>
inline bool FilterOutput<GzipCompress>::flush_filter() {
	bool ret=false;
	auto toflush=pptr()-pbase();
	filter.strm.next_in=reinterpret_cast<unsigned char*>(pbase());
	filter.strm.avail_in=toflush;
	bool flushed=false;
	do {
		int flush=Z_SYNC_FLUSH; //, Z_FINISH
		auto out_delta=filter.strm.avail_out;
		if(out_delta==0) {
			auto c=parent->overflow(EOF);
			if(c==EOF) {
				// XXX
			} else {
				filter.strm.next_out=reinterpret_cast<unsigned char*>(parent->pptr());
				out_delta=filter.strm.avail_out=parent->epptr()-parent->pptr();
			}
		}
		auto r=deflate(&filter.strm, flush);
		out_delta-=filter.strm.avail_out;
		parent->pbump(out_delta);
		switch(r) {
			case Z_OK:
				// continue
				break;
			case Z_BUF_ERROR:
				if(filter.strm.avail_out>0 && filter.strm.avail_in==0)
					flushed=true;
				break;
			case Z_STREAM_END:
			default:
				throwError("failed to deflate");
		}
	} while(!flushed);
	auto delta_in=toflush-filter.strm.avail_in;
	if(delta_in<toflush) {
		if(delta_in>0) {
			setp(pbase()+delta_in, epptr());
			pbump(toflush-delta_in);
		}
	} else {
		ret=true;
		setp(buffer, buffer+BUFSIZ);
	}
	return ret;
}

/* Bzip2
 * gzip is better

struct Bzip2Decompress {
	bz_stream strm;
	struct Params {
		int small;
	};
};
template<>
inline bool FilterInput<Bzip2Decompress>::close() {
	if(!is_open)
		return true;
	if(eof && gptr()==egptr()) {
		auto r=BZ2_bzDecompressEnd(&filter.strm);
		if(r!=BZ_OK) {
			throwError("Failed to destroy bzip2 input filter: ", r);
		}
		is_open=false;
		return true;
	}
	return false;
}
template<>
inline FilterInput<Bzip2Decompress>::~FilterInput() {
	if(is_open) {
		auto r=BZ2_bzDecompressEnd(&filter.strm);
		if(r!=BZ_OK) {
			throwError("Failed to destroy bzip2 input filter: ", r);
		}
	}
}
template<>
inline FilterInput<Bzip2Decompress>::FilterInput(StreamBuf*const p, const Bzip2Decompress::Params& args): FilterInput{p} {
	filter.strm.next_in=parent->gptr();
	filter.strm.avail_in=parent->egptr()-parent->gptr();
	filter.strm.next_out=buffer;
	filter.strm.avail_out=0;
	filter.strm.bzalloc=nullptr;
	filter.strm.bzfree=nullptr;
	filter.strm.opaque=0;
	auto r=BZ2_bzDecompressInit(&filter.strm, 0, args.small);
	if(r!=BZ_OK) {
		throwError("Failed to init bzip2 input filter: ", r);
	}
	is_open=true;
}
template<>
inline std::streamsize FilterInput<Bzip2Decompress>::read_from_upstream(char* s, std::streamsize n) {
	if(eof)
		return 0;
	if(n<=0)
		return 0;
	filter.strm.next_out=s;
	filter.strm.avail_out=n;
	do {
		auto in_delta=filter.strm.avail_in;
		if(in_delta==0) {
			auto c=parent->underflow();
			if(c==EOF) {
			} else {
				filter.strm.next_in=parent->gptr();
				in_delta=filter.strm.avail_in=parent->egptr()-parent->gptr();
			}
		}
		auto r=BZ2_bzDecompress(&filter.strm);
		in_delta-=filter.strm.avail_in;
		parent->gbump(in_delta);
		switch(r) {
			case BZ_OK:
				// continue
				break;
			//case Z_BUF_ERROR:
				// ???
				//break;
			case BZ_STREAM_END:
				eof=true;
				break;
			default:
				throwError("failed to inflate");
		}
	} while(filter.strm.avail_out>0 && !eof);
	auto out_delta=n-filter.strm.avail_out;
	return out_delta;
}
template<>
std::streamoff FilterInput<Bzip2Decompress>::position() {
	std::streamoff pos=filter.strm.total_out_hi32;
	pos=pos<<32+filter.strm.total_out_lo32;
	return pos;
}


struct Bzip2Compress {
	bz_stream strm;
	struct Params {
		int blockSize100k;
		int workFactor;
	};
};

template<>
inline bool FilterOutput<Bzip2Compress>::finish_filter() {
	bool ret=false;
	auto toflush=pptr()-pbase();
	filter.strm.next_in=pbase();
	filter.strm.avail_in=toflush;
	bool flushed=false;
	do {
		int flush=BZ_FINISH;
		auto out_delta=filter.strm.avail_out;
		if(out_delta==0) {
			auto c=parent->overflow(EOF);
			if(c==EOF) {
				// XXX
			} else {
				filter.strm.next_out=parent->pptr();
				out_delta=filter.strm.avail_out=parent->epptr()-parent->pptr();
			}
		}
		auto r=BZ2_bzCompress(&filter.strm, flush);
		out_delta-=filter.strm.avail_out;
		parent->pbump(out_delta);
		switch(r) {
			case BZ_FINISH_OK:
				// continue
				break;
			case BZ_STREAM_END:
				flushed=true;
				break;
			default:
				throwError("failed to deflate");
		}
	} while(!flushed);
	auto delta_in=toflush-filter.strm.avail_in;
	if(delta_in<toflush) {
		if(delta_in>0) {
			setp(pbase()+delta_in, epptr());
			pbump(toflush-delta_in);
		}
	} else {
		ret=true;
		setp(buffer, buffer+BUFSIZ);
	}
	return ret;
}
template<>
inline bool FilterOutput<Bzip2Compress>::close() {
	if(!is_open)
		return true;
	if(!finish_filter())
		return false;
	auto r=BZ2_bzCompressEnd(&filter.strm);
	if(r!=BZ_OK) {
		throwError("Failed to destroy bzip2 output filter: ", r);
	}
	is_open=false;
	return true;
}
template<>
FilterOutput<Bzip2Compress>::~FilterOutput() {
	if(is_open) {
		finish_filter();
		auto r=BZ2_bzCompressEnd(&filter.strm);
		if(r!=BZ_OK) {
			throwError("Failed to destroy bzip2 output filter: ", r);
		}
	}
}
template<>
FilterOutput<Bzip2Compress>::FilterOutput(StreamBuf*const p, const Bzip2Compress::Params& pars): FilterOutput{p} {
	filter.strm.next_in=pbase();
	filter.strm.avail_in=0;
	filter.strm.next_out=parent->pptr();
	filter.strm.avail_out=parent->epptr()-parent->pptr();
	filter.strm.bzalloc=nullptr;
	filter.strm.bzfree=nullptr;
	filter.strm.opaque=nullptr;
	auto r=BZ2_bzCompressInit(&filter.strm, pars.blockSize100k, 0, pars.workFactor);
	if(r!=BZ_OK)
		throwError("Failed to init gzip output filter: ", r);
	is_open=true;
}

template<>
std::streamoff FilterOutput<Bzip2Compress>::position() {
	std::streamoff pos=filter.strm.total_in_hi32;
	pos=pos<<32+filter.strm.total_in_lo32;
	return pos;
}

template<>
inline std::streamsize FilterOutput<Bzip2Compress>::write_to_downstream(const char* s, std::streamsize n) {
	if(n<=0)
		return 0;
	filter.strm.next_in=const_cast<char*>(s);
	filter.strm.avail_in=n;
	do {
		int flush=BZ_RUN;
		auto out_delta=filter.strm.avail_out;
		if(out_delta==0) {
			auto c=parent->overflow(EOF);
			if(c==EOF) {
				// XXX
			} else {
				filter.strm.next_out=parent->pptr();
				out_delta=filter.strm.avail_out=parent->epptr()-parent->pptr();
			}
		}
		auto r=BZ2_bzCompress(&filter.strm, flush);
		out_delta-=filter.strm.avail_out;
		parent->pbump(out_delta);
		switch(r) {
			case BZ_RUN_OK:
				// continue
				break;
			default:
				throwError("failed to deflate");
		}
	} while(filter.strm.avail_in>0);
	auto delta_in=n-filter.strm.avail_in;
	return delta_in;
}

template<>
inline bool FilterOutput<Bzip2Compress>::flush_filter() {
	bool ret=false;
	auto toflush=pptr()-pbase();
	filter.strm.next_in=pbase();
	filter.strm.avail_in=toflush;
	bool flushed=false;
	do {
		int flush=BZ_FLUSH;
		auto out_delta=filter.strm.avail_out;
		if(out_delta==0) {
			auto c=parent->overflow(EOF);
			if(c==EOF) {
				// XXX
			} else {
				filter.strm.next_out=parent->pptr();
				out_delta=filter.strm.avail_out=parent->epptr()-parent->pptr();
			}
		}
		auto r=BZ2_bzCompress(&filter.strm, flush);
		out_delta-=filter.strm.avail_out;
		parent->pbump(out_delta);
		switch(r) {
			case BZ_FLUSH_OK:
				// continue
				break;
			case BZ_RUN_OK:
				flushed=true;
				break;
			default:
				throwError("failed to deflate");
		}
	} while(!flushed);
	auto delta_in=toflush-filter.strm.avail_in;
	if(delta_in<toflush) {
		if(delta_in>0) {
			setp(pbase()+delta_in, epptr());
			pbump(toflush-delta_in);
		}
	} else {
		ret=true;
		setp(buffer, buffer+BUFSIZ);
	}
	return ret;
}
*/

class MemInput: public StreamBuf {
	const std::vector<char*>* bufs;
	size_t bufi;
	size_t bufjunk;
	char dumb;
	std::streamsize bufsize;

	protected:
	std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode which) override {
		std::streamoff ret=-1;
		if(!bufs)
			return ret;
		if(!(std::ios_base::in&which))
			return ret;
		if(way==std::ios_base::cur) {
			if(off+gptr()<=egptr() && off+gptr()>=eback()) {
				gbump(off);
				return bufi*BUFSIZ+(gptr()-eback());
			}
			off=bufi*BUFSIZ+(gptr()-eback())+off;
		} else if(way==std::ios_base::end) {
			off=bufsize+off;
		}
		if(off>=0 && off<bufsize) {
			bufi=off/BUFSIZ;
			auto ptr=(*bufs)[bufi];
			auto len=bufi+1==bufs->size()?BUFSIZ-bufjunk:BUFSIZ;
			setg(ptr, ptr+off%BUFSIZ, ptr+len);
			ret=off;
		} else if(off==bufsize) {
			if(bufs->size()>0) {
				bufi=bufs->size()-1;
				auto ptr=(*bufs)[bufi];
				setg(ptr, ptr+off%BUFSIZ, ptr+BUFSIZ-bufjunk);
				ret=off;
			} else {
				bufi=0;
				setg(&dumb, &dumb, &dumb);
				ret=off;
			}
		}
		return ret;
	}
	std::streampos seekpos(std::streampos sp, std::ios_base::openmode which) override {
		std::streamoff ret=-1;
		if(!bufs)
			return ret;
		if(!(std::ios_base::in&which))
			return ret;
		if(sp>=0 && sp<bufsize) {
			bufi=sp/BUFSIZ;
			auto ptr=(*bufs)[bufi];
			auto len=bufi+1==bufs->size()?BUFSIZ-bufjunk:BUFSIZ;
			setg(ptr, ptr+sp%BUFSIZ, ptr+len);
			ret=sp;
		} else if(sp==bufsize) {
			if(bufs->size()>0) {
				bufi=bufs->size()-1;
				auto ptr=(*bufs)[bufi];
				setg(ptr, ptr+sp%BUFSIZ, ptr+BUFSIZ-bufjunk);
				ret=sp;
			} else {
				bufi=0;
				setg(&dumb, &dumb, &dumb);
				ret=sp;
			}
		}
		return ret;
	}
	int sync() override { return 0; }

	std::streamsize showmanyc() override {
		std::streamsize ret=-1;
		if(bufs)
			ret=bufsize-(bufi*BUFSIZ+gptr()-eback());
		if(ret==0)
			ret=-1;
		return ret;
	}
	std::streamsize xsgetn(char* s, std::streamsize n) override {
		if(!bufs)
			return 0;
		std::streamsize total=n;
		if(n>0)
			n-=StreamBuf::xsgetn(s, n);
		return total-n;
	}
	int underflow() override {
		int ret=EOF;
		if(!bufs)
			return ret;
		if(gptr()<egptr()) {
			ret=traits_type::to_int_type(*gptr());
		} else {
			if(bufi+1<bufs->size()) {
				bufi++;
				auto ptr=(*bufs)[bufi];
				auto len=bufi+1==bufs->size()?BUFSIZ-bufjunk:BUFSIZ;
					setg(ptr, ptr, ptr+len);
				ret=traits_type::to_int_type(*gptr());
			}
		}
		return ret;
	}
	int pbackfail(int c) override {
		int ret=EOF;
		if(!bufs)
			return ret;
		if(eback()<gptr()) {
			gbump(-1);
			if(c==EOF)
				ret=0;
			else
				ret=c;
		} else {
			if(bufi>0) {
				bufi--;
				auto ptr=(*bufs)[bufi];
				setg(ptr, ptr+BUFSIZ-1, ptr+BUFSIZ);
				if(c==EOF)
					ret=0;
				else
					ret=c;
			}
		}
		return ret;
	}

	std::streamsize xsputn(const char* s, std::streamsize n) override {
		std::streamsize ret=0;
		return ret;
	}
	int overflow(int c) override {
		int ret=EOF;
		return ret;
	}

	public:
	//
	MemInput(const std::vector<char*>* _bufs, size_t _size):
		StreamBuf{}, bufs{_bufs}, bufi{0}, bufjunk{0}, bufsize(_size)
	{
		if(!bufs)
			throwError("No buffers given.");
		bufjunk=bufs->size()*BUFSIZ-bufsize;
		if(bufsize<0)
			throwError("Negitive file size.");
		if(bufjunk>=BUFSIZ)
			throwError("Non-positive chuck size.");

		if(0<bufs->size()) {
			auto ptr=(*bufs)[bufi];
			auto len=bufi+1==bufs->size()?BUFSIZ-bufjunk:BUFSIZ;
			setg(ptr, ptr, ptr+len);
		} else {
			setg(&dumb, &dumb, &dumb);
		}
	}
	~MemInput() {
	}
	bool close() override {
		if(bufs)
			bufs=nullptr;
		return true;
	}
};

/*
	gzipcompressor
	gzipdecompressor

	*/

bool InputStream::open(const char* fn) {
	if(!bufs.empty()) {
		this->setstate(ios_base::failbit);
		return false;
	}
	try {
		auto file=new FileInput{fn};
		bufs.push(file);
		std::istream::rdbuf(bufs.top());
		this->clear();
	} catch(...) {
		this->setstate(ios_base::failbit);
		return false;
	}
	return true;
}
bool InputStream::open(FILE* f) {
	if(!bufs.empty()) {
		this->setstate(ios_base::failbit);
		return false;
	}
	try {
		auto file=new FileInput{f};
		bufs.push(file);
		std::istream::rdbuf(bufs.top());
		this->clear();
	} catch(...) {
		this->setstate(ios_base::failbit);
		return false;
	}
	return true;
}
bool InputStream::open(const std::vector<char*>* membufs, size_t bufjunk) {
	if(!bufs.empty()) {
		this->setstate(ios_base::failbit);
		return false;
	}
	try {
		auto memi=new MemInput{membufs, bufjunk};
		bufs.push(memi);
		std::istream::rdbuf(bufs.top());
		this->clear();
	} catch(...) {
		this->setstate(ios_base::failbit);
		return false;
	}
	return true;
}
bool InputStream::pushGzip() {
	if(bufs.empty()) {
		this->setstate(ios_base::failbit);
		return false;
	}
	try {
		GzipDecompress::Params pars;
		pars.windowBits=15+16;
		auto gzip=new FilterInput<GzipDecompress>{bufs.top(), pars};
		bufs.push(gzip);
		std::istream::rdbuf(bufs.top());
		this->clear();
	} catch(...) {
		this->setstate(ios_base::failbit);
		return false;
	}
	return true;
}

bool OutputStream::open(const char* fn) {
	if(!bufs.empty()) {
		this->setstate(ios_base::failbit);
		return false;
	}
	try {
		auto file=new FileOutput{fn};
		bufs.push(file);
		std::ostream::rdbuf(bufs.top());
		this->clear();
	} catch(...) {
		this->setstate(ios_base::failbit);
		return false;
	}
	return true;
}
bool OutputStream::open(FILE* f) {
	if(!bufs.empty()) {
		this->setstate(ios_base::failbit);
		return false;
	}
	try {
		auto file=new FileOutput{f};
		bufs.push(file);
		std::ostream::rdbuf(bufs.top());
		this->clear();
	} catch(...) {
		this->setstate(ios_base::failbit);
		return false;
	}
	return true;
}
bool OutputStream::pushGzip() {
	if(bufs.empty()) {
		this->setstate(ios_base::failbit);
		return false;
	}
	try {
		GzipCompress::Params pars;
		pars.level=5;
		pars.method=Z_DEFLATED;
		pars.windowBits=15+16;
		pars.memLevel=9;
		pars.strategy=Z_FILTERED;
		auto gzip=new FilterOutput<GzipCompress>{bufs.top(), pars};
		bufs.push(gzip);
		std::ostream::rdbuf(bufs.top());
		this->clear();
	} catch(...) {
		this->setstate(ios_base::failbit);
		return false;
	}
	return true;
}

